/*
 This file '_autocomplete' is part of Firebird Integrated Solution 1.0

 Copyright (c) 2015 Lincong

 Contact:  
 Email: lincong1987@gmail.com

 QQ: 159257119

 See Usage at http://www.jplatformx.com/firebird

 Create date: 2015-07-14 04:42
 */


(function ($, undefined) {

// used to prevent race conditions with remote data sources
	var requestIndex = 0;

	$.widget("ui.autocomplete", {
		version: "1.9.2",
		defaultElement: "<input>",
		options: {
			appendTo: "body",
			autoFocus: false,
			delay: 300,
			minLength: 1,
			position: {
				my: "left top",
				at: "left bottom",
				collision: "none"
			},
			source: null,

			// callbacks
			change: null,
			close: null,
			focus: null,
			open: null,
			response: null,
			search: null,
			select: null
		},

		pending: 0,

		_create: function () {
			// Some browsers only repeat keydown events, not keypress events,
			// so we use the suppressKeyPress flag to determine if we've already
			// handled the keydown event. #7269
			// Unfortunately the code for & in keypress is the same as the up arrow,
			// so we use the suppressKeyPressRepeat flag to avoid handling keypress
			// events when we know the keydown event was used to modify the
			// search term. #7799
			var suppressKeyPress, suppressKeyPressRepeat, suppressInput;

			this.isMultiLine = this._isMultiLine();
			this.valueMethod = this.element[this.element.is("input,textarea") ? "val" : "text"];
			this.isNewMenu = true;

			this.element
				.addClass("ui-autocomplete-input")
				.attr("autocomplete", "off");

			this._on(this.element, {
				keydown: function (event) {
					if (this.element.prop("readOnly")) {
						suppressKeyPress = true;
						suppressInput = true;
						suppressKeyPressRepeat = true;
						return;
					}

					suppressKeyPress = false;
					suppressInput = false;
					suppressKeyPressRepeat = false;
					var keyCode = $.ui.keyCode;
					switch (event.keyCode) {
						case keyCode.PAGE_UP:
							suppressKeyPress = true;
							this._move("previousPage", event);
							break;
						case keyCode.PAGE_DOWN:
							suppressKeyPress = true;
							this._move("nextPage", event);
							break;
						case keyCode.UP:
							suppressKeyPress = true;
							this._keyEvent("previous", event);
							break;
						case keyCode.DOWN:
							suppressKeyPress = true;
							this._keyEvent("next", event);
							break;
						case keyCode.ENTER:
						case keyCode.NUMPAD_ENTER:
							// when menu is open and has focus
							if (this.menu.active) {
								// #6055 - Opera still allows the keypress to occur
								// which causes forms to submit
								suppressKeyPress = true;
								event.preventDefault();
								this.menu.select(event);
							}
							break;
						case keyCode.TAB:
							if (this.menu.active) {
								this.menu.select(event);
							}
							break;
						case keyCode.ESCAPE:
							if (this.menu.element.is(":visible")) {
								this._value(this.term);
								this.close(event);
								// Different browsers have different default behavior for escape
								// Single press can mean undo or clear
								// Double press in IE means clear the whole form
								event.preventDefault();
							}
							break;
						default:
							suppressKeyPressRepeat = true;
							// search timeout should be triggered before the input value is changed
							this._searchTimeout(event);
							break;
					}
				},
				keypress: function (event) {
					if (suppressKeyPress) {
						suppressKeyPress = false;
						event.preventDefault();
						return;
					}
					if (suppressKeyPressRepeat) {
						return;
					}

					// replicate some key handlers to allow them to repeat in Firefox and Opera
					var keyCode = $.ui.keyCode;
					switch (event.keyCode) {
						case keyCode.PAGE_UP:
							this._move("previousPage", event);
							break;
						case keyCode.PAGE_DOWN:
							this._move("nextPage", event);
							break;
						case keyCode.UP:
							this._keyEvent("previous", event);
							break;
						case keyCode.DOWN:
							this._keyEvent("next", event);
							break;
					}
				},
				input: function (event) {
					if (suppressInput) {
						suppressInput = false;
						event.preventDefault();
						return;
					}
					this._searchTimeout(event);
				},
				focus: function () {
					this.selectedItem = null;
					this.previous = this._value();
				},
				blur: function (event) {
					if (this.cancelBlur) {
						delete this.cancelBlur;
						return;
					}

					clearTimeout(this.searching);
					this.close(event);
					this._change(event);
				}
			});

			this._initSource();
			this.menu = $("<ul>")
				.addClass("ui-autocomplete")
				.appendTo(this.document.find(this.options.appendTo || "body")[0])
				.menu({
					// custom key handling for now
					input: $(),
					// disable ARIA support, the live region takes care of that
					role: null
				})
				.zIndex(this.element.zIndex() + 1)
				.hide()
				.data("menu");

			this._on(this.menu.element, {
				mousedown: function (event) {
					// prevent moving focus out of the text field
					event.preventDefault();

					// IE doesn't prevent moving focus even with event.preventDefault()
					// so we set a flag to know when we should ignore the blur event
					this.cancelBlur = true;
					this._delay(function () {
						delete this.cancelBlur;
					});

					// clicking on the scrollbar causes focus to shift to the body
					// but we can't detect a mouseup or a click immediately afterward
					// so we have to track the next mousedown and close the menu if
					// the user clicks somewhere outside of the autocomplete
					var menuElement = this.menu.element[0];
					if (!$(event.target).closest(".ui-menu-item").length) {
						this._delay(function () {
							var that = this;
							this.document.one("mousedown", function (event) {
								if (event.target !== that.element[0] &&
									event.target !== menuElement && !$.contains(menuElement, event.target)) {
									that.close();
								}
							});
						});
					}
				},
				menufocus: function (event, ui) {
					// #7024 - Prevent accidental activation of menu items in Firefox
					if (this.isNewMenu) {
						this.isNewMenu = false;
						if (event.originalEvent && /^mouse/.test(event.originalEvent.type)) {
							this.menu.blur();

							this.document.one("mousemove", function () {
								$(event.target).trigger(event.originalEvent);
							});

							return;
						}
					}

					// back compat for _renderItem using item.autocomplete, via #7810
					// TODO remove the fallback, see #8156
					var item = ui.item.data("ui-autocomplete-item") || ui.item.data("item.autocomplete");
					if (false !== this._trigger("focus", event, {item: item})) {
						// use value to match what will end up in the input, if it was a key event
						if (event.originalEvent && /^key/.test(event.originalEvent.type)) {
							this._value(item.value);
						}
					} else {
						// Normally the input is populated with the item's value as the
						// menu is navigated, causing screen readers to notice a change and
						// announce the item. Since the focus event was canceled, this doesn't
						// happen, so we update the live region so that screen readers can
						// still notice the change and announce it.
						this.liveRegion.text(item.value);
					}
				},
				menuselect: function (event, ui) {
					// back compat for _renderItem using item.autocomplete, via #7810
					// TODO remove the fallback, see #8156
					var item = ui.item.data("ui-autocomplete-item") || ui.item.data("item.autocomplete"),
						previous = this.previous;

					// only trigger when focus was lost (click on menu)
					if (this.element[0] !== this.document[0].activeElement) {
						this.element.focus();
						this.previous = previous;
						// #6109 - IE triggers two focus events and the second
						// is asynchronous, so we need to reset the previous
						// term synchronously and asynchronously :-(
						this._delay(function () {
							this.previous = previous;
							this.selectedItem = item;
						});
					}

					if (false !== this._trigger("select", event, {item: item})) {
						this._value(item.value);
					}
					// reset the term after the select event
					// this allows custom select handling to work properly
					this.term = this._value();

					this.close(event);
					this.selectedItem = item;
				}
			});

			this.liveRegion = $("<span>", {
				role: "status",
				"aria-live": "polite"
			})
				.addClass("ui-helper-hidden-accessible")
				.insertAfter(this.element);

			if ($.fn.bgiframe) {
				this.menu.element.bgiframe();
			}

			// turning off autocomplete prevents the browser from remembering the
			// value when navigating through history, so we re-enable autocomplete
			// if the page is unloaded before the widget is destroyed. #7790
			this._on(this.window, {
				beforeunload: function () {
					this.element.removeAttr("autocomplete");
				}
			});
		},

		_destroy: function () {
			clearTimeout(this.searching);
			this.element
				.removeClass("ui-autocomplete-input")
				.removeAttr("autocomplete");
			this.menu.element.remove();
			this.liveRegion.remove();
		},

		_setOption: function (key, value) {
			this._super(key, value);
			if (key === "source") {
				this._initSource();
			}
			if (key === "appendTo") {
				this.menu.element.appendTo(this.document.find(value || "body")[0]);
			}
			if (key === "disabled" && value && this.xhr) {
				this.xhr.abort();
			}
		},

		_isMultiLine: function () {
			// Textareas are always multi-line
			if (this.element.is("textarea")) {
				return true;
			}
			// Inputs are always single-line, even if inside a contentEditable element
			// IE also treats inputs as contentEditable
			if (this.element.is("input")) {
				return false;
			}
			// All other element types are determined by whether or not they're contentEditable
			return this.element.prop("isContentEditable");
		},

		_initSource: function () {
			var array, url,
				that = this;
			if ($.isArray(this.options.source)) {
				array = this.options.source;
				this.source = function (request, response) {
					response($.ui.autocomplete.filter(array, request.term));
				};
			} else if (typeof this.options.source === "string") {
				url = this.options.source;
				this.source = function (request, response) {
					if (that.xhr) {
						that.xhr.abort();
					}
					that.xhr = $.ajax({
						url: url,
						data: request,
						dataType: "json",
						success: function (data) {
							response(data);
						},
						error: function () {
							response([]);
						}
					});
				};
			} else {
				this.source = this.options.source;
			}
		},

		_searchTimeout: function (event) {
			clearTimeout(this.searching);
			this.searching = this._delay(function () {
				// only search if the value has changed
				if (this.term !== this._value()) {
					this.selectedItem = null;
					this.search(null, event);
				}
			}, this.options.delay);
		},

		search: function (value, event) {
			value = value != null ? value : this._value();

			// always save the actual value, not the one passed as an argument
			this.term = this._value();

			if (value.length < this.options.minLength) {
				return this.close(event);
			}

			if (this._trigger("search", event) === false) {
				return;
			}

			return this._search(value);
		},

		_search: function (value) {
			this.pending++;
			this.element.addClass("ui-autocomplete-loading");
			this.cancelSearch = false;

			this.source({term: value}, this._response());
		},

		_response: function () {
			var that = this,
				index = ++requestIndex;

			return function (content) {
				if (index === requestIndex) {
					that.__response(content);
				}

				that.pending--;
				if (!that.pending) {
					that.element.removeClass("ui-autocomplete-loading");
				}
			};
		},

		__response: function (content) {
			if (content) {
				content = this._normalize(content);
			}
			this._trigger("response", null, {content: content});
			if (!this.options.disabled && content && content.length && !this.cancelSearch) {
				this._suggest(content);
				this._trigger("open");
			} else {
				// use ._close() instead of .close() so we don't cancel future searches
				this._close();
			}
		},

		close: function (event) {
			this.cancelSearch = true;
			this._close(event);
		},

		_close: function (event) {
			if (this.menu.element.is(":visible")) {
				this.menu.element.hide();
				this.menu.blur();
				this.isNewMenu = true;
				this._trigger("close", event);
			}
		},

		_change: function (event) {
			if (this.previous !== this._value()) {
				this._trigger("change", event, {item: this.selectedItem});
			}
		},

		_normalize: function (items) {
			// assume all items have the right format when the first item is complete
			if (items.length && items[0].label && items[0].value) {
				return items;
			}
			return $.map(items, function (item) {
				if (typeof item === "string") {
					return {
						label: item,
						value: item
					};
				}
				return $.extend({
					label: item.label || item.value,
					value: item.value || item.label
				}, item);
			});
		},

		_suggest: function (items) {
			var ul = this.menu.element
				.empty()
				.zIndex(this.element.zIndex() + 1);
			this._renderMenu(ul, items);
			this.menu.refresh();

			// size and position menu
			ul.show();
			this._resizeMenu();
			ul.position($.extend({
				of: this.element
			}, this.options.position));

			if (this.options.autoFocus) {
				this.menu.next();
			}
		},

		_resizeMenu: function () {
			var ul = this.menu.element;
			ul.outerWidth(Math.max(
				// Firefox wraps long text (possibly a rounding bug)
				// so we add 1px to avoid the wrapping (#7513)
				ul.width("").outerWidth() + 1,
				this.element.outerWidth()
			));
		},

		_renderMenu: function (ul, items) {
			var that = this;
			$.each(items, function (index, item) {
				that._renderItemData(ul, item);
			});
		},

		_renderItemData: function (ul, item) {
			return this._renderItem(ul, item).data("ui-autocomplete-item", item);
		},

		_renderItem: function (ul, item) {
			return $("<li>")
				.append($("<a>").text(item.label))
				.appendTo(ul);
		},

		_move: function (direction, event) {
			if (!this.menu.element.is(":visible")) {
				this.search(null, event);
				return;
			}
			if (this.menu.isFirstItem() && /^previous/.test(direction) ||
				this.menu.isLastItem() && /^next/.test(direction)) {
				this._value(this.term);
				this.menu.blur();
				return;
			}
			this.menu[direction](event);
		},

		widget: function () {
			return this.menu.element;
		},

		_value: function () {
			return this.valueMethod.apply(this.element, arguments);
		},

		_keyEvent: function (keyEvent, event) {
			if (!this.isMultiLine || this.menu.element.is(":visible")) {
				this._move(keyEvent, event);

				// prevents moving cursor to beginning/end of the text field in some browsers
				event.preventDefault();
			}
		}
	});

	$.extend($.ui.autocomplete, {
		escapeRegex: function (value) {
			return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
		},
		filter: function (array, term) {
			var matcher = new RegExp($.ui.autocomplete.escapeRegex(term), "i");
			return $.grep(array, function (value) {
				return matcher.test(value.label || value.value || value);
			});
		}
	});

// live region extension, adding a `messages` option
// NOTE: This is an experimental API. We are still investigating
// a full solution for string manipulation and internationalization.
	$.widget("ui.autocomplete", $.ui.autocomplete, {
		options: {
			messages: {
				noResults: "No search results.",
				results: function (amount) {
					return amount + ( amount > 1 ? " results are" : " result is" ) +
						" available, use up and down arrow keys to navigate.";
				}
			}
		},

		__response: function (content) {
			var message;
			this._superApply(arguments);
			if (this.options.disabled || this.cancelSearch) {
				return;
			}
			if (content && content.length) {
				message = this.options.messages.results(content.length);
			} else {
				message = this.options.messages.noResults;
			}
			this.liveRegion.text(message);
		}
	});

}(jQuery));